﻿// 2023/3/8: 首个版本
// 2023/9/22: 使用PassParent给ShowDialog传参
// 2023/9/25: 改为直接输入Icon对象
// 2024/1/16: 修正timestampsToTable异常

using System;
using System.Collections.Generic;
using System.IO;
using ASEva;
using ASEva.Samples;
using ASEva.UIEto;
using Eto.Forms;
using Eto.Drawing;

namespace CommonSource
{
    class TimestampsViewer : DialogPanel
    {
        public TimestampsViewer(String dataTitle, Timestamp[] timestamps, Icon icon = null)
        {
            Icon = icon;
            Title = dataTitle + " " + (chinese ? "时间戳" : "Timestamps");
            SetFixMode(1180, 500, true);

            var layout = this.SetContentAsColumnLayout();
            var textTable = layout.AddControl(new TextTableView(), true) as TextTableView;
            var buttonLayout = layout.AddRowLayout();
            var buttonExport = buttonLayout.AddButton(chinese ? "导出" : "Export", false, 100);
            buttonLayout.AddSpace();
            buttonLayout.AddButton("OK").Click += delegate { Close(); };

            textTable.AddColumn(chinese ? "时间偏置[ms]" : "Time Offset[ms]", 120, false);
            textTable.AddColumn(chinese ? "同步状态" : "Sync State", 80, false);
            textTable.AddColumn(chinese ? "到达时CPU时间[ms]" : "Arrival CPU Time[ms]", 250, false);
            textTable.AddColumn(chinese ? "到达时主机POSIX[ms]" : "Arrival Host POSIX[ms]", 250, false);
            textTable.AddColumn(chinese ? "采样时客机POSIX[ms]" : "Sampling Guest POSIX[ms]", 180, false);
            textTable.AddColumn(chinese ? "采样时卫星POSIX[ms]" : "Sampling GNSS POSIX[ms]", 250, false);

            rowTexts = timestampsToTable(timestamps, out noReal, out syncSource);

            var list = new List<String[]>();
            for (int i = 0; i < rowTexts.GetLength(0); i++)
            {
                var arr = new String[rowTexts.GetLength(1)];
                for (int j = 0; j < arr.Length; j++) arr[j] = rowTexts[i, j];
                list.Add(arr);
            }
            textTable.AddRows(list);

            timer.Interval = 0.1;
            timer.Elapsed += delegate
            {
                timer.Stop();
                foreach (var coord in noReal)
                {
                    textTable.SetTextColor(coord.X, coord.Y, Colors.DarkGray);
                }
                foreach (var coord in syncSource)
                {
                    textTable.SetTextColor(coord.X, coord.Y, Colors.Green);
                }
            };
            timer.Start();

            buttonExport.Click += delegate
            {
                var dialog = new SaveFileDialog();
                dialog.Filters.Add(new FileFilter(chinese ? "CSV文件" : "CSV File", ".csv"));
                if (dialog.ShowDialog(App.PassParent(this)) != DialogResult.Ok) return;

                var filePath = dialog.FileName;
                if (!filePath.ToLower().EndsWith(".csv")) filePath += ".csv";

                using (var writer = new StreamWriter(filePath))
                {
                    var firstRow = new List<String>();
                    firstRow.Add(chinese ? "时间偏置[ms]" : "Time Offset[ms]");
                    firstRow.Add(chinese ? "同步状态" : "Sync State");
                    firstRow.Add(chinese ? "到达时CPU时间[ms]" : "Arrival CPU Time[ms]");
                    firstRow.Add(chinese ? "到达时主机POSIX[ms]" : "Arrival Host POSIX[ms]");
                    firstRow.Add(chinese ? "采样时客机POSIX[ms]" : "Sampling Guest POSIX[ms]");
                    firstRow.Add(chinese ? "采样时卫星POSIX[ms]" : "Sampling GNSS POSIX[ms]");
                    writer.WriteLine(String.Join(',', firstRow));

                    for (int i = 0; i < rowTexts.GetLength(0); i++)
                    {
                        var texts = new String[6];
                        for (var n = 0; n < 6; n++) texts[n] = rowTexts[i, n];
                        writer.WriteLine(String.Join(',', texts));
                    }
                }

                Agency.PopupNotice(chinese ? "已导出CSV文件。" : "CSV file exported.");
            };
        }

        public override void OnClosing()
        {
            timer.Stop();
        }

        private String[,] timestampsToTable(Timestamp[] timestamps, out IntPoint[] noReal, out IntPoint[] syncSource)
        {
            noReal = new IntPoint[0];
            syncSource = new IntPoint[0];

            if (timestamps == null || timestamps.Length == 0) return new String[0, 6];

            var cpuModels = new Dictionary<SessionIdentifier, CPUTimeModel>();
            var hostModels = new Dictionary<SessionIdentifier, PosixTimeModel>();
            var gnssModels = new Dictionary<SessionIdentifier, PosixTimeModel>();

            foreach (var timestamp in timestamps)
            {
                if (cpuModels.ContainsKey(timestamp.Session)) continue;
                cpuModels[timestamp.Session] = Agency.GetCPUTimeModel(timestamp.Session.ToDateTime());
                hostModels[timestamp.Session] = Agency.GetHostPosixTimeModel(timestamp.Session.ToDateTime());
                gnssModels[timestamp.Session] = Agency.GetGNSSPosixTimeModel(timestamp.Session.ToDateTime());
            }

            var output = new String[timestamps.Length, 6];
            var noRealList = new List<IntPoint>();
            var syncSourceList = new List<IntPoint>();

            var list = new List<String[]>();
            for (int i = 0; i < timestamps.Length; i++)
            {
                var timestamp = timestamps[i];

                output[i, 0] = (timestamp.Offset * 1000).ToString("F6");

                // sync state
                output[i, 1] = "NONE";
                bool guestGreen = false, gnssGreen = false;
                if (timestamp.TimeInfo != null)
                {
                    switch (timestamp.TimeInfo.OffsetSync)
                    {
                        case TimeOffsetSync.Server:
                            output[i, 1] = "SERV";
                            guestGreen = true;
                            break;
                        case TimeOffsetSync.Gnss:
                            output[i, 1] = "GNSS";
                            gnssGreen = true;
                            break;
                    }
                }

                // cpu time
                ulong cpuTimeCalculated = 0, cpuTimeReal = 0;
                if (cpuModels.ContainsKey(timestamp.Session))
                {
                    var model = cpuModels[timestamp.Session];
                    if (model != null)
                    {
                        var cpuTimeSecCalculated = (double)model.StartCPUTick / model.CPUTicksPerSecond + timestamp.Offset;
                        cpuTimeCalculated = (ulong)(cpuTimeSecCalculated * 1000000000);
                        if (timestamp.TimeInfo != null && timestamp.TimeInfo.CPUTick != 0)
                        {
                            var cpuTimeSecReal = (double)timestamp.TimeInfo.CPUTick / model.CPUTicksPerSecond;
                            cpuTimeReal = (ulong)(cpuTimeSecReal * 1000000000);
                        }
                    }
                }
                if (cpuTimeCalculated == 0)
                {
                    output[i, 2] = "-";
                    noRealList.Add(new IntPoint(i, 2));

                }
                else if (cpuTimeReal == 0)
                {
                    output[i, 2] = posixToString(cpuTimeCalculated);
                    noRealList.Add(new IntPoint(i, 2));
                }
                else output[i, 2] = dualPosixToString(cpuTimeReal, cpuTimeCalculated);

                // host posix
                ulong hostPosixCalculated = 0, hostPosixReal = 0;
                if (hostModels.ContainsKey(timestamp.Session))
                {
                    var model = hostModels[timestamp.Session];
                    if (model != null) hostPosixCalculated = model.StartPosix * 1000000 + (ulong)(timestamp.Offset * model.TimeRatio * 1000000000);
                }
                if (timestamp.TimeInfo != null) hostPosixReal = timestamp.TimeInfo.HostPosix;
                if (hostPosixCalculated == 0)
                {
                    if (hostPosixReal == 0)
                    {
                        output[i, 3] = "-";
                        noRealList.Add(new IntPoint(i, 3));
                    }
                    else output[i, 3] = posixToString(hostPosixReal);
                }
                else
                {
                    if (hostPosixReal == 0)
                    {
                        output[i, 3] = posixToString(hostPosixCalculated);
                        noRealList.Add(new IntPoint(i, 3));
                    }
                    else output[i, 3] = dualPosixToString(hostPosixReal, hostPosixCalculated);
                }

                // guest posix
                ulong guestPosixReal = 0;
                if (timestamp.TimeInfo != null) guestPosixReal = timestamp.TimeInfo.GuestPosix;
                if (guestPosixReal == 0)
                {
                    output[i, 4] = "-";
                    noRealList.Add(new IntPoint(i, 4));
                }
                else
                {
                    output[i, 4] = posixToString(guestPosixReal);
                    if (guestGreen) syncSourceList.Add(new IntPoint(i, 4));
                }

                // gnss posix
                ulong gnssPosixCalculated = 0, gnssPosixReal = 0;
                if (gnssModels.ContainsKey(timestamp.Session))
                {
                    var model = gnssModels[timestamp.Session];
                    if (model != null) gnssPosixCalculated = model.StartPosix * 1000000 + (ulong)(timestamp.Offset * model.TimeRatio * 1000000000);
                }
                if (timestamp.TimeInfo != null) gnssPosixReal = timestamp.TimeInfo.GNSSPosix;
                if (gnssPosixReal == 0)
                {
                    output[i, 5] = "-";
                    noRealList.Add(new IntPoint(i, 5));
                }
                else
                {
                    if (gnssPosixCalculated == 0) output[i, 5] = posixToString(gnssPosixReal);
                    else output[i, 5] = dualPosixToString(gnssPosixReal, gnssPosixCalculated);
                    if (gnssGreen) syncSourceList.Add(new IntPoint(i, 5));
                }
            }

            noReal = noRealList.ToArray();
            syncSource = syncSourceList.ToArray();
            return output;
        }

        private static String posixToString(ulong posixNano)
        {
            var posixMilli = posixNano / 1000000;
            var extraNano = posixNano % 1000000;
            return posixMilli + "." + extraNano.ToString("D6");
        }

        private static String dualPosixToString(ulong posixNanoReal, ulong posixNanoCalculated)
        {
            if (posixNanoReal >= posixNanoCalculated) return posixToString(posixNanoReal) + "(+" + posixToString(posixNanoReal - posixNanoCalculated) + ")";
            else return posixToString(posixNanoReal) + "(-" + posixToString(posixNanoCalculated - posixNanoReal) + ")";
        }

        private UITimer timer = new UITimer();

        private String[,] rowTexts = null;
        private IntPoint[] noReal = null, syncSource = null;

        private bool chinese = Agency.GetAppLanguage() == "ch";
    }
}